package com.shxzz.easy12306.services.ticket.service.impl;

import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.shxzz.easy12306.framework.starter.cache.StringRedisTemplateProxy;
import com.shxzz.easy12306.framework.starter.cache.toolkit.CacheUtil;
import com.shxzz.easy12306.framework.starter.common.dto.order.TicketOrderDetailRespDTO;
import com.shxzz.easy12306.framework.starter.common.dto.order.TicketOrderPassengerDetailRespDTO;
import com.shxzz.easy12306.framework.starter.convention.exceptions.RemoteException;
import com.shxzz.easy12306.framework.starter.convention.result.Result;
import com.shxzz.easy12306.services.ticket.common.constant.RedisKeyConstant;
import com.shxzz.easy12306.services.ticket.common.enums.SeatStatusEnum;
import com.shxzz.easy12306.services.ticket.config.TicketRemainCacheUpdateProperties;
import com.shxzz.easy12306.services.ticket.dao.entity.SeatCountDO;
import com.shxzz.easy12306.services.ticket.dao.entity.SeatDO;
import com.shxzz.easy12306.services.ticket.dao.mapper.SeatMapper;
import com.shxzz.easy12306.services.ticket.dto.domain.RouteDTO;
import com.shxzz.easy12306.services.ticket.dto.domain.SelectSeatDTO;
import com.shxzz.easy12306.services.ticket.remote.OrderRemoteService;
import com.shxzz.easy12306.services.ticket.service.SeatService;
import com.shxzz.easy12306.services.ticket.service.TrainStationService;
import com.shxzz.easy12306.services.ticket.service.handler.dto.SeatSelectRespDTO;
import com.shxzz.easy12306.services.ticket.service.handler.tokenbucket.TicketPurchaseTokenBucket;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.shxzz.easy12306.services.ticket.config.TicketRemainCacheUpdateProperties.BINLOG_UPDATE_TYPE;

/**
 * 座位服务层实现
 *
 * @author shxzz
 * @create 2024-03-07-23:52
 */
@RequiredArgsConstructor
@Service
public class SeatServiceImpl implements SeatService {

    private final TicketRemainCacheUpdateProperties ticketRemainCacheUpdateProperties;

    private final SeatMapper seatMapper;

    private final TrainStationService trainStationService;
    private final OrderRemoteService orderRemoteService;

    private final TicketPurchaseTokenBucket ticketPurchaseTokenBucket;

    private final StringRedisTemplateProxy stringRedisTemplateProxy;

    @Override
    public List<String> listAvailableCarriageNumber(String trainId, Integer seatType, String departure, String arrival) {
        LambdaQueryWrapper<SeatDO> queryWrapper = Wrappers.lambdaQuery(SeatDO.class)
                .eq(SeatDO::getTrainId, trainId)
                .eq(SeatDO::getSeatType, seatType)
                .eq(SeatDO::getStartStation, departure)
                .eq(SeatDO::getEndStation, arrival)
                .eq(SeatDO::getSeatStatus, SeatStatusEnum.AVAILABLE.getCode())
                .groupBy(SeatDO::getCarriageNumber)
                .select(SeatDO::getCarriageNumber);

        List<SeatDO> seatDOList = seatMapper.selectList(queryWrapper);
        return seatDOList.stream().map(SeatDO::getCarriageNumber).toList();
    }

    @Override
    public List<SeatCountDO> countCarriageRemainTicketBySeatType(String trainId, Integer seatType, String departure, String arrival) {
        SeatDO seatDO = SeatDO.builder()
                .trainId(Long.valueOf(trainId))
                .seatType(seatType)
                .startStation(departure)
                .endStation(arrival)
                .build();

        return seatMapper.countRemainTicketByCarriage(seatDO);
    }

    @Override
    public List<String> listAvailableSeats(String trainId, String carriageNumber, Integer seatType, String departure, String arrival) {

        LambdaQueryWrapper<SeatDO> queryWrapper = Wrappers.lambdaQuery(SeatDO.class)
                .eq(SeatDO::getTrainId, trainId)
                .eq(SeatDO::getCarriageNumber, carriageNumber)
                .eq(SeatDO::getStartStation, departure)
                .eq(SeatDO::getEndStation, arrival)
                .eq(SeatDO::getSeatType, seatType)
                .eq(SeatDO::getSeatStatus, SeatStatusEnum.AVAILABLE.getCode())
                .select(SeatDO::getSeatNumber);
        List<SeatDO> seatDOList = seatMapper.selectList(queryWrapper);
        return seatDOList.stream().map(SeatDO::getSeatNumber).toList();
    }

    @Override
    public void lockSeat(String trainId, List<SeatSelectRespDTO> seatSelectRespDTOlist, List<RouteDTO> routeDTOList) {
        seatSelectRespDTOlist.forEach(each -> routeDTOList.forEach(routeDTO -> {
            LambdaUpdateWrapper<SeatDO> updateWrapper = Wrappers.lambdaUpdate(SeatDO.class)
                    .eq(SeatDO::getTrainId, trainId)
                    .eq(SeatDO::getCarriageNumber, each.getCarriageNumber())
                    .eq(SeatDO::getSeatNumber, each.getSeatNumber())
                    .eq(SeatDO::getStartStation, routeDTO.getStartStation())
                    .eq(SeatDO::getEndStation, routeDTO.getEndStation());
            SeatDO seatDO = SeatDO.builder().seatStatus(SeatStatusEnum.LOCKED.getCode()).build();
            seatMapper.update(seatDO, updateWrapper);
        }));
    }

    @Override
    public void unlockSeat(String trainId, List<RouteDTO> routeDTOList, List<SelectSeatDTO> selectSeatDTOList) {
        selectSeatDTOList.forEach(each -> routeDTOList.forEach(routeDTO -> {
            LambdaUpdateWrapper<SeatDO> updateWrapper = Wrappers.lambdaUpdate(SeatDO.class)
                    .eq(SeatDO::getTrainId, trainId)
                    .eq(SeatDO::getCarriageNumber, each.getCarriageNumber())
                    .eq(SeatDO::getSeatNumber, each.getSeatNumber())
                    .eq(SeatDO::getStartStation, routeDTO.getStartStation())
                    .eq(SeatDO::getEndStation, routeDTO.getEndStation());
            SeatDO seatDO = SeatDO.builder().seatStatus(SeatStatusEnum.AVAILABLE.getCode()).build();
            seatMapper.update(seatDO, updateWrapper);
        }));
    }

    @Override
    public void rollbackTicketRemainCache(List<SelectSeatDTO> selectSeatDTOList, List<RouteDTO> routeDTOList, String trainId) {
        Map<Integer, Long> seatTypeCountMap = selectSeatDTOList.stream()
                .collect(Collectors.groupingBy(SelectSeatDTO::getSeatType, Collectors.counting()));
        StringRedisTemplate stringRedisTemplate = stringRedisTemplateProxy.getInstance();
        stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
            seatTypeCountMap.forEach((key, value) -> routeDTOList.forEach(routeDTO -> {
                String routeKey = CacheUtil.buildKey(RedisKeyConstant.TRAIN_REMAIN_TICKET_PREFIX, trainId, routeDTO.getStartStation(), routeDTO.getEndStation());
                connection.hashCommands().hIncrBy(routeKey.getBytes(), key.toString().getBytes(), value);
            }));
            return null;
        });
    }

    @Override
    public void rollbackSeatStatus(String trainId, String departure, String arrival, List<SelectSeatDTO> selectSeatDTOList) {
        List<RouteDTO> routeDTOList = trainStationService.listTakeoutTrainStationRoute(trainId, departure, arrival);
        unlockSeat(trainId, routeDTOList, selectSeatDTOList);
        ticketPurchaseTokenBucket.rollbackToken(selectSeatDTOList, routeDTOList, trainId);
        if(!CharSequenceUtil.equals(ticketRemainCacheUpdateProperties.getTicketUpdateType(), BINLOG_UPDATE_TYPE)) {
            rollbackTicketRemainCache(selectSeatDTOList, routeDTOList, trainId);
        }
    }

    @Override
    public void payCallback(String orderSn) {
        Result<TicketOrderDetailRespDTO> ticketOrderDetailRespDTOResult = orderRemoteService.queryTicketOrderByOrderSn(orderSn);
        if(!ticketOrderDetailRespDTOResult.isSuccess()) {
            throw new RemoteException("远程查询订单详情失败");
        }
        TicketOrderDetailRespDTO ticketOrderDetailRespDTO = ticketOrderDetailRespDTOResult.getData();
        List<TicketOrderPassengerDetailRespDTO> passengerDetails = ticketOrderDetailRespDTO.getPassengerDetails();
        passengerDetails.forEach(each -> {
            LambdaUpdateWrapper<SeatDO> updateWrapper = Wrappers.lambdaUpdate(SeatDO.class)
                    .eq(SeatDO::getTrainId, ticketOrderDetailRespDTO.getTrainId())
                    .eq(SeatDO::getCarriageNumber, each.getCarriageNumber())
                    .eq(SeatDO::getSeatNumber, each.getSeatNumber())
                    .eq(SeatDO::getSeatType, each.getSeatType())
                    .eq(SeatDO::getStartStation, ticketOrderDetailRespDTO.getDeparture())
                    .eq(SeatDO::getEndStation, ticketOrderDetailRespDTO.getArrival());
            seatMapper.update(SeatDO.builder().seatStatus(SeatStatusEnum.SOLD.getCode()).build(), updateWrapper);
        });
    }
}
